﻿namespace BathroomLocator.Services
{
	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.Linq;
	using System.Net;
	using System.ServiceModel;
	using System.ServiceModel.Activation;
	using System.ServiceModel.Web;
	using System.Web;
	using System.Web.Security;
	using BathroomLocator.Services.Infrastructure;
	using BathroomLocator.Services.MembershipWrappers;
	using Microsoft.WindowsAzure;
	using Microsoft.WindowsAzure.StorageClient;
	using WindowsPhone.Recipes.Push.Messasges;

	[ServiceBehavior(IncludeExceptionDetailInFaults = false)]
	[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
	public class SamplePushUserRegistrationService : ISamplePushUserRegistrationService
	{
		private readonly HttpContextBase context;
		private readonly IPushUserEndpointsRepository pushUserEndpointsRepository;
		private readonly IFormsAuthentication formsAuth;
		private readonly CloudQueueClient cloudQueueClient;
		private readonly WebOperationContext webOperationContext;

		public SamplePushUserRegistrationService()
			: this(new HttpContextWrapper(HttpContext.Current), WebOperationContext.Current, new FormsAuthenticationService(), new PushUserEndpointContext(), null)
		{
		}

		public SamplePushUserRegistrationService(HttpContextBase context, WebOperationContext webOperationContext, IFormsAuthentication formsAuth, IPushUserEndpointsRepository pushUserEndpointsRepository, CloudQueueClient cloudQueueClient)
		{
			if ((context == null) && (HttpContext.Current == null))
			{
				throw new ArgumentNullException("context", "Context cannot be null if not running on a Web context.");
			}

			if (pushUserEndpointsRepository == null)
			{
				throw new ArgumentNullException("pushUserEndpointsRepository", "PushUserEndpoints repository cannot be null.");
			}

			if (formsAuth == null)
			{
				throw new ArgumentNullException("formsAuth", "Forms Authentication service cannot be null.");
			}

			if ((cloudQueueClient == null) && (GetStorageAccountFromConfigurationSetting() == null))
			{
				throw new ArgumentNullException("cloudQueueClient", "Cloud Queue Client cannot be null if no configuration is loaded.");
			}

			this.cloudQueueClient = cloudQueueClient ?? GetStorageAccountFromConfigurationSetting().CreateCloudQueueClient();
			this.webOperationContext = webOperationContext;
			this.context = context;
			this.pushUserEndpointsRepository = pushUserEndpointsRepository;
			this.formsAuth = formsAuth;
		}

		private string UserName
		{
			get
			{
				string ticketValue = null;

				var cookie = this.context.Request.Cookies[this.formsAuth.FormsCookieName];
				if (cookie != null)
				{
					// From cookie.
					ticketValue = cookie.Value;
				}
				else if (this.context.Request.Headers["AuthToken"] != null)
				{
					// From HTTP header.
					ticketValue = this.context.Request.Headers["AuthToken"];
				}

				if (!string.IsNullOrEmpty(ticketValue))
				{
					FormsAuthenticationTicket ticket;

					try
					{
						ticket = this.formsAuth.Decrypt(ticketValue);
					}
					catch
					{
						throw new WebFaultException<string>("The authorization ticket cannot be decrypted.", HttpStatusCode.Unauthorized);
					}

					if (ticket != null)
					{
						return new FormsIdentity(ticket).Name;
					}
					else
					{
						throw new WebFaultException<string>("The authorization token is no longer valid.", HttpStatusCode.Unauthorized);
					}
				}
				else
				{
					throw new WebFaultException<string>("Resource not found.", HttpStatusCode.NotFound);
				}
			}
		}

		public string Register(Uri channelUri)
		{
			// Authenticate.
			var userName = this.UserName;

			try
			{
				if (this.pushUserEndpointsRepository.GetUsersByNameAndEndpoint(userName, channelUri).Count() == 0)
				{
					this.pushUserEndpointsRepository.AddUserEndpoint(userName, channelUri);
				}
			}
			catch (Exception exception)
			{
				throw new WebFaultException<string>(string.Format("There was an error registering the Push Notification Endpoint: {0}", exception.Message), HttpStatusCode.InternalServerError);
			}

			return "Success";
		}

		public string Unregister(Uri channelUri)
		{
			// Authenticate.
			var userName = this.UserName;

			try
			{
				this.pushUserEndpointsRepository.RemoveUserEndpoint(userName, channelUri);
			}
			catch (Exception exception)
			{
				throw new WebFaultException<string>(string.Format("There was an error unregistering the Push Notification Endpoint: {0}", exception.Message), HttpStatusCode.InternalServerError);
			}

			return "Success";
		}

		public string[] GetUpdates(Uri channelUri)
		{
			// Authenticate.
			var userName = this.UserName;

			this.webOperationContext.OutgoingResponse.Headers.Add("Cache-Control", "no-cache");

			try
			{
				var queueName = string.Format(CultureInfo.InvariantCulture, "notification{0}", channelUri.GetHashCode());
				var queue = this.cloudQueueClient.GetQueueReference(queueName);
				var messages = new List<string>();
				if (queue.Exists())
				{
					var message = queue.GetMessage();
					while (message != null)
					{
						messages.Add(message.AsString);
						queue.DeleteMessage(message);
						message = queue.GetMessage();
					}
				}

				this.ResetTileNotificationCount(channelUri);

				return messages.ToArray();
			}
			catch (Exception exception)
			{
				throw new WebFaultException<string>(string.Format("There was an error getting the push notification updates: {0}", exception.Message), HttpStatusCode.InternalServerError);
			}
		}

		private static CloudStorageAccount GetStorageAccountFromConfigurationSetting()
		{
			CloudStorageAccount account = null;

			try
			{
				account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
			}
			catch (InvalidOperationException)
			{
				account = null;
			}

			return account;
		}

		private void ResetTileNotificationCount(Uri channelUri)
		{
			var resultList = new List<MessageSendResultLight>();
			var pushUserEndpointList = this.pushUserEndpointsRepository.GetUsersByNameAndEndpoint(this.UserName, channelUri);

			foreach (var pushUserEndpoint in pushUserEndpointList)
			{
				pushUserEndpoint.TileCount = 0;

				var tile = new TilePushNotificationMessage
				{
					SendPriority = MessageSendPriority.High,
					Count = pushUserEndpoint.TileCount
				};

				var messageResult = tile.SendAndHandleErrors(new Uri(pushUserEndpoint.ChannelUri));

				this.pushUserEndpointsRepository.UpdateUserEndpoint(pushUserEndpoint);
			}
		}
	}
}